﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EasyStep.Data.Entities;
using EasyStep.Business;
using EasyStep.Business.Interfaces;
using EasyStep.Common;
using EasyStep.TestExecution.Interfaces;
using EasyStep.TestExecution.Data;
using OpenQA.Selenium;
using System.IO;
using OpenQA.Selenium.Support.UI;
using System.Threading;

namespace EasyStep.TestExecution
{
    public class TestExecutor : ITestExecutor
    {
        public IExecuteDataContext DataContext { get; set; }
        private bool ScreenshotAfterEachStep;
        private int PauseAfterEachStepFor;
        private bool StopOnFirstFailed;
        public event TestStepEventHandler OnBeforeExecuteStep;
        public event TestStepEventHandler OnAfterExecuteStep;
        public event TestStepEventHandler OnBeforeExecuteChildStep;
        public event TestStepEventHandler OnAfterExecuteChildStep;
        public event EventHandler OnExecutionCompleted;


        private Stack<TestStep> StepsToExecute { get; set; }
        public TestStepResult CurrentResult = null;
        public TestExecutor(IExecuteDataContext dataContext)
        {
            this.DataContext = dataContext;
        }

        private bool paused = false;

        public void Run(TestInfo test, bool screenshotAfterEachStep = false, bool stopOnFirstFailed = false, int pauseAfterEachStepFor = 100)
        {
            ScreenshotAfterEachStep = screenshotAfterEachStep;
            PauseAfterEachStepFor = pauseAfterEachStepFor;
            StopOnFirstFailed = stopOnFirstFailed;
            var steps = test.Steps.Reverse();
            StepsToExecute = new Stack<TestStep>(steps);
            UpdateTestHistory(new TestStepResult(null) { Browser = DataContext.BrowserInfo.Name }); ;
            DataContext.BrowserInstance = DataContext.BrowserInfo.GetNewInstance(DataContext);
            Pause(false);
        }

        public void Pause(bool valueToSet)
        {
            paused = valueToSet;
            DataContext.Log.Info((valueToSet) ? "Automatically run next step been paused!TestExecutor.Pause" : "Automatically run test been started");
            while (!paused && Next())
            {
                if (PauseAfterEachStepFor > 0) Thread.Sleep(PauseAfterEachStepFor);
                DataContext.Log.Debug("Automatically run next step been completed!TestExecutor.Run");
            }
        }

        public bool Next()
        {
            DataContext.Log.Info("Start Execution Next Test Step:");
            var result = true;
            var e = new ExecuteEventArgs() { Cancel = false, TestStepResult = GetNextTestStep() };
            if (e.TestStepResult == null)
            {
                DataContext.Log.Info("All Test Steps been processed!");
                this.Dispose();
                return false;
            }
            //raise event onBefore Run Test step
            if (OnBeforeExecuteStep != null) OnBeforeExecuteStep(this, e);
            if (!e.Cancel)
                ExecuteStep(e.TestStepResult);
            else
                DataContext.Log.Info("Execution Test Step been Skipped by OnBeforeExecuteStep");
            UpdateTestHistory(e.TestStepResult);

            //raise event onAfter Run Test step            
            if (OnAfterExecuteStep != null) OnAfterExecuteStep(this, e);
            DataContext.Log.Info(string.Format("{1} - Finished. Test Step:{0}", DataContext.TestHistoryManager.Convert(e.TestStepResult), e.TestStepResult.Result));
            if (e.TestStepResult.Result == ExecutionResult.Failed && StopOnFirstFailed)
            {
                result = false;
                DataContext.Log.Info("Test stopped. Because last step is Failed and (StopOnFirstFailed=true)");
                this.Stop();
            }
            return result;
        }


        private TestStepResult UpdateTestHistory(TestStepResult tStep)
        {
            DataContext.Log.Debug("Try to store test result into repository");

            DataContext.TestHistoryManager.SaveResult(
                DataContext.CurrentProject,
                DataContext.CurrentTest,
                DataContext.CurrentSessionName,
                tStep
            );


            DataContext.Log.Debug("test result has been saved");
            return tStep;
        }
        private TestStepResult ExecuteStep(TestStepResult data)
        {
            DataContext.Log.Info(string.Format("Try to execute test step: {0}", DataContext.TestHistoryManager.Convert(data), data));
            DataContext.BrowserInfo.ExecuteStep(DataContext, data);
            if (data.StepInfo.Action.RequireScreenShot || ScreenshotAfterEachStep)
                DataContext.BrowserInfo.MakeScreenshot(DataContext, data);

            var children=data.Children != null && data.Children.Any()?data.Children.ToArray():null;
            if (children!=null)
            try
            {
                foreach (var child in children){
                        child.Parent = data;
                        var e = new ExecuteEventArgs() { Cancel = false, TestStepResult = PrepareTestStepToExecute(child) };
                        DataContext.Log.Debug(string.Format("try to execute [Child Test Step]", DataContext.TestHistoryManager.Convert(e.TestStepResult), e.TestStepResult));
                        if (OnBeforeExecuteChildStep != null)
                        {
                            OnBeforeExecuteChildStep(this, e);
                            if (e.Cancel)
                            {
                                DataContext.Log.Info("Execution [Child Test Step] been canceled by OnBeforeExecuteChildStep");
                                continue;
                            }
                        }
                        ExecuteStep(e.TestStepResult);
                        child.Result = e.TestStepResult.Result;
                        UpdateTestHistory(e.TestStepResult);
                        if (OnAfterExecuteChildStep != null)
                            OnAfterExecuteChildStep(this, e);
                        DataContext.Log.Info("Finished [Child Test Step]");
                        data.ExecutionLog.AppendFormat("child-{0} result:{1}\r\n", e.TestStepResult.StepInfo.OrderNr, e.TestStepResult.Result);
                        if (e.TestStepResult.Result == ExecutionResult.Failed && StopOnFirstFailed)
                        {
                            DataContext.Log.Info("child steps execution been stopped. Because last step is Failed and (StopOnFirstFailed=true)");
                            break;
                        }
                    }
            }
            catch (Exception ex)
            {
                data.Result = ExecutionResult.Failed;
                data.ExecutionLog.AppendFormat("\r\n{0}\r\n", ex.ToString());
                DataContext.Log.Error(string.Format("can't finish test step, exception has been raised!{0}", DataContext.TestStepManager.Convert(data.StepInfo)), ex);
            }
            if (data.Result != ExecutionResult.Failed && children != null && children.Any())
            {
                if (children.Where(c => c.Result == ExecutionResult.Failed).Any())
                    data.Result = ExecutionResult.Failed;
            }
            data.Result = data.Result == ExecutionResult.Failed
                ? ExecutionResult.Failed
                : ExecutionResult.Success;

            DataContext.Log.Debug("test step execution has been completed");
            return data;
        }



        private TestStepResult GetNextTestStep()
        {
            DataContext.Log.Debug("Try to find next Test Step to execute it:");///todo:add here ex info... about current step ,Nr
            if (StepsToExecute == null || StepsToExecute.Count == 0) return null;
            CurrentResult = PrepareTestStepToExecute(StepsToExecute.Pop());
            return CurrentResult;
        }

        private TestStepResult PrepareTestStepToExecute(TestStep data)
        {
            if (data == null) return null;
            return PrepareTestStepToExecute(new TestStepResult(new TestStep()
            {
                Id = data.Id,
                ActionId = data.ActionId,
                OrderNr = data.OrderNr,
                TestId = data.TestId,
                Test = data.Test,
                Data = data.Data,
                Locator = data.Locator
            }));
        }
        private TestStepResult PrepareTestStepToExecute(TestStepResult data)
        {
            var result = data;
            result.Browser = DataContext.BrowserInfo.Name;
            result.Result = ExecutionResult.InProgress;
            result.StepInfo.Locator = ReplaceConstantWithValue(result.StepInfo.Locator);
            result.StepInfo.Data = ReplaceConstantWithValue(result.StepInfo.Data);
            result.StepInfo.Action = DataContext.ActionManager.Get(result.StepInfo.ActionId);
            return result;
        }

        private TestData ReplaceConstantWithValue(TestData data)
        {
            var result = new TestData() { Value = data == null ? null : data.Value };
            if (result.Value == null || !result.Value.StartsWith("c:")) return result;

            var cname = result.Value.Remove(0, 2);
            var constItem = DataContext.ConstantsManager.Get(cname);
            if (constItem == null)
            {
                DataContext.Log.Error("Error!Trying to ReplaceConstantWithValue to execute test.There is no constant with name:" + cname + ",project:" + DataContext.CurrentProject);
                return result;
            }
            result.Value = constItem.Value;
            result.ConstId = constItem.Id;

            return result;
        }


        public void Stop()
        {
            DataContext.Log.Info(string.Format("Test executor stops test:{0}, {1}", DataContext.CurrentTest, DataContext.CurrentSessionName));
            try
            {
                Pause(true);
                if (OnExecutionCompleted != null)
                {
                    OnExecutionCompleted(this, new EventArgs());
                    OnExecutionCompleted -= OnExecutionCompleted;
                }
            }
            catch (Exception ex)
            {

                DataContext.Log.Fatal(string.Format("Error!Test executor CAN'T CORRECTLY stops test:{0}, {1}, {2}", DataContext.BrowserInfo.Name, DataContext.CurrentTest, DataContext.CurrentSessionName), ex);
            }
            finally
            {
                if (DataContext.BrowserInstance != null)
                {
                    DataContext.BrowserInfo.FinalizeInstance(DataContext.BrowserInstance);
                }
            }
        }

        public void Dispose()
        {
            DataContext.Log.Info(string.Format("Test executor finalized:{0}, {1}", DataContext.CurrentTest, DataContext.CurrentSessionName));
            DataContext.Log.Debug("Test executor reference has been Dispose();");

            Stop();
        }
    }
}
